#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/user.h>
#include <unistd.h>
#include <signal.h>

#include <um.h>
#include <ptrace.h>
#include <process.h>
#include <stdio.h>
#include <console.h>
#include <clock.h>
#include <intr.h>
#include <assert.h>
#include <stub.h>
#include <memlayout.h>


static char* sig_names[] = {
	"",  /* 0 */
	"",
	"SIGINT",
	"",
	"",
	"SIGTRAP",  /* 5 */
	"",
	"",
	"",
	"",
	"SIGUSR1",  /* 10 */
	"SIGSEGV",
	"",
	"",
	"",
	"",  /* 15 */
	"",
	"SIGCHLD",
	"",
	"",
	"",  /* 20 */
	"",
	"",
	"",
	"",
	"",  /* 25 */
	"",
};


/**
 * cause current process starting tracing its childs
 * @param pid the pid of the child
 */
void
um_ptrace (int pid) 
{
	int status;
	siginfo_t siginfo;
	int running = 1;
	struct user_regs_struct regs;

	/* status = PTRACE_O_TRACESYSGOOD; */
	/* ptrace (PTRACE_SETOPTIONS, pid, 0, 0, &status); */
	while (running) {
		wait (&status);
		if (WIFEXITED (status) || WIFSIGNALED (status)) {
			cprintf ("exited\n");
			break;
		}
		ptrace (PTRACE_GETSIGINFO, pid, 0, &siginfo);
		switch (siginfo.si_signo) {
		case SIGVTALRM:
		case SIGIO:
			ptrace (PTRACE_CONT, pid, 0, siginfo.si_signo);
			break;
		case SIGUSR1:
		case SIGINT:
			running = 0;
			break;
		case SIGSTOP:
		case SIGSEGV:
			ptrace (PTRACE_GETREGS, pid, 0, &regs);
			cprintf ("[%s]\tesp = 0x%x\teip=0x%x\n", sig_names[siginfo.si_signo], regs.esp, regs.eip);
			if (siginfo.si_signo != SIGSEGV)
				ptrace (PTRACE_CONT, pid, 0, 0);
			break;
		case SIGTRAP:
			ptrace (PTRACE_GETREGS, pid, 0, &regs);
			cprintf ("[%s]\tesp = 0x%x\teax=0x%x\tebx=0x%x\n", sig_names[siginfo.si_signo], regs.esp, regs.eax, regs.ebx);
			ptrace (PTRACE_CONT, pid, 0, 0);
			break;
		default:
			cprintf ("Unknown signal: %d\n", siginfo.si_signo);
			ptrace (PTRACE_KILL, pid, 0, 0);
			running = 0;
			;
			//ptrace (PTRACE_KILL, pid, 0, 0);
		}
	}
}


/**
 * Resume anything we have changed.
 */
void
child_terminal ()
{
	cons_dtor ();
}

/**
 * Handling SIGIO and exit if SPACE is typed.
 */
void
child_sigio (int dummy)
{
	int c = cons_getc ();
	cprintf ("[%d] %c\n", c, (char)c);
	if (c == 32) {
		raise (SIGUSR1);  /* Inform the tracing thread that all are done. */
		_exit (0);
	}
}


/**
 * entry of the traced children
 */
void
ptraced_child (void) 
{
	/* signal (SIGIO, child_sigio); */

	int pid = um_getpid ();
	
	/* cprintf ("Child's pid: %d\n", pid); */
	if (ptrace (PTRACE_TRACEME, 0, NULL, NULL) < 0)
		goto fail;

	/* set SIGSEGV's handler */
	struct sigaction sa;
	int segv_handler = UM_STUB_CODE + (int)stub_segv_handler - (int)&__syscall_stub_start;
	stack_t stack = (stack_t) {
		.ss_flags = 0,
		.ss_sp = (void*)UM_STUB_DATA,
		.ss_size = PGSIZE - sizeof (void*)
	};
	if (sigaltstack (&stack, NULL) != 0)
		goto fail;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_ONSTACK | SA_NODEFER;
	sa.sa_handler = (void *) segv_handler;
	sa.sa_restorer = NULL;
	if (sigaction (SIGSEGV, &sa, NULL) < 0)
		goto fail;

	void (*stub_start_ptr)(void) = (void*)((int)stub_entry + UM_STUB_CODE - (int)&__syscall_stub_start);

	stub_start_ptr ();
	
	while (1);

fail:
	kill (pid, SIGKILL);
}
